﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Reflection;
using Managistics.Core.Infrastructure;
using Managistics.Core.Repositories;
using Managistics.Core.Services;
using Managistics.Model;
using Managistics.Utility.Common;
using OfficeOpenXml;

namespace Managistics.Core.Services.Impl
{
    public class InventoryService : IInventoryService
    {
        const string DELIMITER = "\t\t | \t\t";

        private readonly IGoodRepository _goodRepository;
        private readonly IOutputRepository _outputRepository;
        private readonly IAdjustInStockRepository _adjustRepository;

        public InventoryService(IGoodRepository goodRepository, IOutputRepository outputRepository, IAdjustInStockRepository adjustRepository)
        {
            this._goodRepository = goodRepository;
            this._outputRepository = outputRepository;
            this._adjustRepository = adjustRepository;
        }

        [UnitOfWork]
        public int ImportToCreateRelations(string filePath, int startRow)
        {
            // Old good records
            GoodDataRecord oldRecord = null;
            GoodDataRecord currentRecord = null;
            int currentRowNumber = 0;
            filePath = "D:\\SimpleListRelations.xlsx";
            try
            {
                if (File.Exists(filePath))
                {
                    IList<GoodDataRecord> goodDataList = new List<GoodDataRecord>();

                    // Get the file we are going to process
                    var existingFile = new FileInfo(filePath); 
                    // Open and read the XlSX file.
                    using (var package = new ExcelPackage(existingFile))
                    {
                        // Get the work book in the file
                        ExcelWorkbook workBook = package.Workbook;
                        if (workBook != null)
                        {
                            if (workBook.Worksheets.Count > 0)
                            {
                                // Get the first worksheet
                                ExcelWorksheet currentWorksheet = workBook.Worksheets.First();

                                for (int rowNumber = startRow; rowNumber <= currentWorksheet.Dimension.End.Row; rowNumber++)
                                // read each row from the start of the data (start row + 1 header row) to the end of the spreadsheet.
                                {
                                    currentRowNumber = rowNumber;
                                    object goodCodeValue = currentWorksheet.Cells[rowNumber, 1].Value;
                                    object unitNameValue = currentWorksheet.Cells[rowNumber, 4].Value;
                                    // xx: Retail code, huong: Parity code
                                    object relationValue = currentWorksheet.Cells[rowNumber, 5].Value;

                                    if (goodCodeValue == null)
                                    {
                                        break;
                                    }

                                    string unitName = string.Empty;
                                    int convertUnit = 0;

                                    // set values for current record
                                    currentRecord = new GoodDataRecord()
                                    {
                                        GoodCode = goodCodeValue.ToString().PadLeft(6, '0'),
                                        UnitName = unitNameValue.ToString(),
                                        // xx or hương
                                        Relation = (relationValue != null ? relationValue.ToString() : string.Empty)
                                    };

                                    // if not set relation, blank
                                    if (string.IsNullOrEmpty(currentRecord.Relation))
                                    {
                                        oldRecord = null;
                                    }
                                    else if (currentRecord.Relation.Equals("hương"))
                                    {
                                        // remove any characters, only get 4 first characters
                                        int limit = (currentRecord.UnitName.Length >= 4 ? 3 : currentRecord.UnitName.Length - 1);
                                        int check = 0;

                                        unitName = Regex.Replace(currentRecord.UnitName.Substring(0, limit), @"[^0-9]", string.Empty);
                                        if (!String.IsNullOrEmpty(unitName) && int.TryParse(unitName, out convertUnit))
                                        {
                                            currentRecord.ConvertUnit = convertUnit;
                                        }
                                        else
                                        {
                                            //Console.WriteLine(String.Format(rowNumber + "{0}" + currentRecord + "{0}" + "unit not valid", DELIMITER));
                                            check++;
                                        }

                                        // check the old good not xx: Retail code
                                        if (oldRecord == null || (oldRecord != null && !oldRecord.Relation.ToLower().Equals("xx")))
                                        {
                                            //Console.WriteLine(String.Format(rowNumber + "{0}" + currentRecord + "{0}" + "Parity code not valid", DELIMITER));
                                            check++;
                                        }

                                        // check to save object to database
                                        if (check == 2)
                                        {
                                            Console.WriteLine("unit not valid && parity code not valid");
                                        }
                                        else if (check == 0 && oldRecord.Relation.ToLower().Equals("xx"))
                                        {
                                            // ma chan
                                            Good ParityGood = _goodRepository.GetGoodById(currentRecord.GoodCode);
                                            // ma le
                                            Good RetailGood = _goodRepository.GetGoodById(oldRecord.GoodCode);

                                            // save to database
                                            if (ParityGood != null && RetailGood != null)
                                            {
                                                // update by repository
                                                RetailGood.Paritycode = ParityGood.Code;
                                                RetailGood.Convertunit = currentRecord.ConvertUnit;

                                                _goodRepository.Update(RetailGood);

                                                Console.WriteLine("---------updated good--------");
                                                Console.WriteLine(String.Format(rowNumber + "{0}" + RetailGood.Code + "{0}", DELIMITER));
                                            }
                                            else
                                            {
                                                Console.WriteLine("---------good not found--------");
                                                if (ParityGood == null)
                                                {
                                                    Console.WriteLine(String.Format(rowNumber + "{0}" + ParityGood.Code + "{0}" + "not found", DELIMITER));
                                                }
                                                if (RetailGood == null)
                                                {
                                                    Console.WriteLine(String.Format(rowNumber + "{0}" + RetailGood.Code + "{0}" + "not found", DELIMITER));
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        oldRecord = currentRecord;
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {

                }
            }
            catch (IOException ioEx)
            {
                if (!String.IsNullOrEmpty(ioEx.Message))
                {
                    if (ioEx.Message.Contains("because it is being used by another process."))
                    {
                        Console.WriteLine("Could not read example data. Please make sure it not open in Excel.");
                    }
                }
                Console.WriteLine("Could not read example data. " + ioEx.Message, ioEx);
            }
            catch (Exception ex)
            {
                Console.WriteLine("An error occured while reading example data. " + ex.Message, ex);
                Console.WriteLine(String.Format(currentRowNumber + "{0}" + currentRecord + "{0}" + "error", DELIMITER));
            }
            finally
            {
                Console.WriteLine("finished.");
            }
            return 0;
        }

        public static string GetDelimiter()
        {
            return DELIMITER;
        }

        # region For Adjust
        /// <summary>
        /// Get parity goods to adjust in month
        /// </summary>
        /// <param name="parameters">Managistics.Core.Services.AdjustParityRetailVO</param>
        /// <returns></returns>
        [UnitOfWork]
        public IList<AdjustParityRetailRecord> GetParityGoods2AdjustInMonth(AdjustParityRetailVO parameters)
        {
            // get all parity goods has outputed quantity for customer in month/year
            IList<OutputSheetDetail> parityGoodsOutputed = this._outputRepository.GetParityGoodsOutputedInMonth(parameters.AdjustMonth, parameters.AdjustYear);
            List<AdjustParityRetailRecord> goods2Adjust = new List<AdjustParityRetailRecord>();

            // sum outputed quanity by good code
            if (this._adjustRepository.HasAdjustedInMonth(parameters.AdjustMonth, parameters.AdjustYear, string.Empty))
            {
                // get all goods already adjusted in month
                IList<AdjustInStock> goodsAdjusted = this._adjustRepository.GetGoodsAdjustedInMonth(parameters.AdjustMonth, parameters.AdjustYear, string.Empty);

                // update outputed quantity of goods had adjusted
                goods2Adjust = (from a in goodsAdjusted
                                join g in GlobalApp.AllGoods on a.Good.Code equals g.Code
                                select new AdjustParityRetailRecord
                                {
                                    GoodCode = a.Good.Code,
                                    GoodName = g.Name,
                                    ParityCodeOfRetail = a.ParityCode,
                                    ConversionUnit = a.ConversionUnit,
                                    ProductCode = g.ProductCode,
                                    UnitName = g.UnitItem.Name,
                                    ParityOutputedQuantity = (StringExtensions.IsNullOrWhiteSpace(a.ParityCode) ? Convert.ToInt32(Math.Ceiling(parityGoodsOutputed.Single(o => o.ItemId == a.Good.Code).Quantity)) : 0),
                                    AdjustRetailOutputQuantity = a.AdjustRetailOutputQuantity,
                                    AdjustParityInputQuantity = a.AdjustParityInputQuantity
                                }).ToList();
                string[] parityGoodsAry = (from i in goods2Adjust
                                           where StringExtensions.IsNullOrWhiteSpace(i.ParityCodeOfRetail)
                                           select i.GoodCode).ToArray<string>();

                // add outputed quantity of new goods not adjust
                var groupParityGoods = (from outputed in parityGoodsOutputed
                                        group outputed by outputed.ItemId into Grouped
                                        join i in GlobalApp.AllGoods on Grouped.FirstOrDefault().ItemId equals i.Code
                                        select new
                                        {
                                            GoodCode = Grouped.FirstOrDefault().ItemId,
                                            GoodName = i.Name,
                                            ConversionUnit = i.Convertunit,
                                            ProductCode = i.ProductCode,
                                            UnitName = i.UnitItem.Name,
                                            TotalOutputedQuantity = Grouped.Sum(g => g.Quantity)
                                        });
                foreach (var item in groupParityGoods)
                {
                    if (!parityGoodsAry.Contains(item.GoodCode))
                    {
                        Good retail = GlobalApp.AllGoods.Single(g => g.Paritycode == item.GoodCode);
                        // retail good first
                        goods2Adjust.Add(new AdjustParityRetailRecord
                        {
                            GoodCode = retail.Code,
                            GoodName = retail.Name,
                            ParityCodeOfRetail = retail.Paritycode,
                            ConversionUnit = retail.Convertunit,
                            ProductCode = retail.ProductCode,
                            UnitName = retail.UnitItem.Name,
                            ParityOutputedQuantity = 0,
                            AdjustRetailOutputQuantity = 0,
                            AdjustParityInputQuantity = 0
                        });
                        // parity good first
                        goods2Adjust.Add(new AdjustParityRetailRecord
                        {
                            GoodCode = item.GoodCode,
                            GoodName = item.GoodName,
                            ParityCodeOfRetail = string.Empty,
                            ConversionUnit = item.ConversionUnit,
                            ProductCode = item.ProductCode,
                            UnitName = item.UnitName,
                            ParityOutputedQuantity = Convert.ToInt32(Math.Ceiling(item.TotalOutputedQuantity)),
                            AdjustRetailOutputQuantity = 0,
                            AdjustParityInputQuantity = 0
                        });
                    }
                }
            }
            else
            {
                // never adjust in month
                goods2Adjust = (from outputed in parityGoodsOutputed
                                group outputed by outputed.ItemId into Grouped
                                join goods in GlobalApp.AllGoods on Grouped.FirstOrDefault().ItemId equals goods.Code
                                orderby Grouped.FirstOrDefault().ItemId ascending
                                select new AdjustParityRetailRecord
                                {
                                    GoodCode = Grouped.FirstOrDefault().ItemId,
                                    ParityCodeOfRetail = string.Empty,
                                    GoodName = goods.Name,
                                    ProductCode = goods.ProductCode,
                                    UnitName = goods.UnitItem.Name,
                                    ConversionUnit = 0,
                                    ParityOutputedQuantity = Convert.ToInt32(Math.Ceiling(Grouped.Sum(g => g.Quantity))),
                                    AdjustRetailOutputQuantity = 0,
                                    AdjustParityInputQuantity = 0
                                })
                                .ToList<AdjustParityRetailRecord>();

                // get retail goods
                string[] parityGoodsAry = (from i in goods2Adjust select i.GoodCode).ToArray<string>();

                List<AdjustParityRetailRecord> retailGoods = (from g in GlobalApp.AllGoods
                                                              where g.Paritycode != null && parityGoodsAry.Contains(g.Paritycode)
                                                              select new AdjustParityRetailRecord
                                                              {
                                                                  GoodCode = g.Code,
                                                                  ParityCodeOfRetail = g.Paritycode,
                                                                  GoodName = g.Name,
                                                                  ProductCode = g.ProductCode,
                                                                  UnitName = g.UnitItem.Name,
                                                                  ConversionUnit = g.Convertunit.Value,
                                                                  ParityOutputedQuantity = 0,
                                                                  AdjustRetailOutputQuantity = 0,
                                                                  AdjustParityInputQuantity = 0
                                                              }).ToList<AdjustParityRetailRecord>();

                // merge two goods list
                goods2Adjust = goods2Adjust.Concat(retailGoods).ToList<AdjustParityRetailRecord>();

                // move items in list
                foreach (string parityCode in parityGoodsAry)
                {
                    int indexOfParity = goods2Adjust.Select((v, i) => new { item = v, index = i }).First(g => g.item.GoodCode == parityCode).index;
                    int newIndex = (indexOfParity > 0 ? indexOfParity : 0);

                    Predicate<AdjustParityRetailRecord> whereParityCode = new Predicate<AdjustParityRetailRecord>(g => g.ParityCodeOfRetail == parityCode);

                    goods2Adjust.Move<AdjustParityRetailRecord>(whereParityCode, newIndex);
                }

            }

            return goods2Adjust;
        }

        /// <summary>
        /// Execute adjust goods stock in month
        /// </summary>
        /// <param name="parameters">Managistics.Core.Services.AdjustParityRetailVO</param>
        /// <returns>The result number of goods adjusted</returns>
        [UnitOfWork]
        public int ExecuteAdjustInMonth(AdjustParityRetailVO parameters)
        {
            // remove all adjust sheets
            if (this._adjustRepository.HasAdjustedInMonth(parameters.AdjustMonth, parameters.AdjustYear, string.Empty))
            {
                this.RemoveAllAdjustInMonth(parameters.AdjustMonth, parameters.AdjustYear);
            }

            // add new adjust
            IList<AdjustParityRetailRecord> goods2Adjust = this.GetParityGoods2AdjustInMonth(parameters);

            foreach (AdjustParityRetailRecord item in goods2Adjust)
            {
                this._adjustRepository.SaveOrUpdateGood(new AdjustInStock
                {
                    Good = GlobalApp.AllGoods.Single(g => g.Code == item.GoodCode),
                    ParityCode = item.ParityCodeOfRetail,
                    Employee = GlobalApp.AllEmployees.Single(e => e.Code == "EMP0000000005"),
                    AdjustedDate = DateTime.Now,
                    GoodPrice = 0,
                    ConversionUnit = item.ConversionUnit,
                    OutputedQuantity = item.ParityOutputedQuantity,
                    AdjustRetailOutputQuantity = item.AdjustRetailOutputQuantity,
                    AdjustParityInputQuantity = item.AdjustParityInputQuantity,
                    Note = String.Empty,
                    CreatedDate = DateTime.Now,
                    UpdatedDate = DateTime.Now
                });
            }

            return 1;
        }

        /// <summary>
        /// Remove all adjust sheets in month/year
        /// </summary>
        /// <param name="month"></param>
        /// <param name="year"></param>
        /// <returns></returns>
        /// no use UnitOfWork
        public bool RemoveAllAdjustInMonth(int month, int year)
        {
            return (this._adjustRepository.RemoveAllAdjustInMonth(month, year) > 0);
        }

        # endregion
    }

    class GoodDataRecord
    {
        public string GoodCode { get; set; }
        public string UnitName { get; set; }
        public string Relation { get; set; }
        public int ConvertUnit { get; set; }

        public override string ToString()
        {
            return String.Format(GoodCode + "{0}" + UnitName + "{0}" + Relation + "{0}"  + ConvertUnit, InventoryService.GetDelimiter());
        }
    }
}